﻿using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using UMC.Net;

namespace UMC.ITME
{

    class HttpMimeRequest : UMC.Net.Mime, IDisposable
    {
        public virtual void StatusCode(int statusCode)
        {

        }

        public StringValue _host = StringValue.Empty;
        public StringValue _UserAgent = StringValue.Empty;
        public StringValue _Challenge = StringValue.Empty;
        public StringValue _XProxy = StringValue.Empty;
        public StringValue _scheme;
        protected override bool Header(StringValue key, MimeHeadKey headKey, StringValue value)
        {
            switch (headKey)
            {
                case MimeHeadKey.XForwardedHost:
                    return XForwardedHost(value);
                case MimeHeadKey.IfChallenge:
                    _Challenge = value;
                    return false;
                case MimeHeadKey.Connection:
                    _IsUpgrade = value.Contains("upgrade"u8, true);
                    break;
                case MimeHeadKey.Host:
                    this.Host(value);
                    break;
                case MimeHeadKey.XRealIP:
                case MimeHeadKey.XForwardedFor:
                    return XRealIP(value);
                case MimeHeadKey.UserAgent:
                    _UserAgent = value;
                    UserAgent(value);
                    break;
                case MimeHeadKey.XForwardedProxy:

                    _XProxy = value;
                    // XForwardedProxy(value);
                    return false;
                case MimeHeadKey.Referer:
                    this.netLoger.Write(HttpLogerType.Referer, value);
                    break;
                case MimeHeadKey.XForwardedProto:
                    XForwardedProto(value);
                    return false;
                case MimeHeadKey.Upgrade:
                    _isWebSocket = Utility.EqualIgnoreCase(value.Span, "websocket"u8);
                    break;
                case MimeHeadKey.Cookie:
                    int begin = 0;
                    while (begin < value.Span.Length)
                    {
                        var vEnd = NetClient.IndexOf(value.Span, "; "u8, begin);
                        var vIndex = NetClient.IndexOf(value.Span, "="u8, begin);
                        if (vEnd > 0)
                        {
                            if (vEnd > vIndex && vIndex > 0)
                            {
                                AddCookie(value.Slice(begin, vIndex - begin), value.Slice(vIndex + 1, vEnd - vIndex - 1));
                            }
                            else
                            {
                                AddCookie(value.Slice(begin, vEnd - begin), StringValue.Empty);
                            }
                            begin = vEnd + 2;
                        }
                        else
                        {
                            if (vIndex > 0)
                            {
                                AddCookie(value.Slice(begin, vIndex - begin), value.Slice(vIndex + 1));

                            }
                            else
                            {
                                AddCookie(value.Slice(begin), StringValue.Empty);
                            }
                            break;
                        }
                    }
                    break;
            }
            return true;
        }

        protected virtual void UserAgent(StringValue value)
        {
            this.netLoger.Write(HttpLogerType.UserAgent, value);
        }


        protected virtual void AddCookie(StringValue name, StringValue value)
        {

            _Cookies.Add(new HeadValue(name, value));
        }
        protected virtual void XForwardedProto(StringValue value)
        {

        }
        protected virtual bool XRealIP(StringValue value)
        {
            if (value.Span.Contains((byte)'.') == false && value.Span.Contains((byte)':') == false)
            {
                if (_context is HttpMimeSocket)
                {
                    try
                    {
                        var ip = new byte[16];
                        var ipvalue = new StringValue(ip, 0, Utility.DecodeBase64(value.Span, ip));
                        ((HttpMimeSocket)_context)._remoteIpAddress = ipvalue;// new StringValue(ip, 0, Utility.DecodeBase64(value.Span, ip));
                        this.netLoger.Write(HttpLogerType.IP, ipvalue);

                        return false;
                    }
                    catch
                    {
                        this.IsHttpFormatError = true;
                    }
                }
            }
            return true;
        }

        protected virtual bool XForwardedHost(StringValue value)
        {
            return true;
        }
        protected virtual void Host(StringValue value)
        {
            this.IsHttpFormatError = value.Length > 63 && Utility.IsHost(value.Span) == false;
            if (this.IsHttpFormatError == false && _host.IsEmpty)
            {
                _host = value;
                this.netLoger.Write(HttpLogerType.Host, value);
            }

        }


        public HeadValues Cookies => _Cookies;
        protected HeadValues _Cookies = new HeadValues();

        internal HttpMime _context;
        internal long startTime;
        internal HttpLoger netLoger;
        public HttpMimeRequest(HttpMime context, ByteChunk memory, HttpLoger loger) : base(memory)
        {
            context.IsWrited = false;
            this.netLoger = loger;
            this._context = context;
            this._scheme = context.IsSSL ? StringValue.Https : StringValue.Http;//"http";
            this.startTime = UMC.Data.Reflection.TimeSpanMilli(DateTime.Now);


        }
        protected override StringValue PathValue(StringValue stringValue)
        {
            var span = stringValue.Span;
            if (span.StartsWith("https://"u8))
            {
                this._scheme = StringValue.Https;
                int l = span.IndexOf((byte)'/', 10);
                if (l > 0)
                {
                    _host = stringValue.Slice(8, l - 8);

                    netLoger.Write(HttpLogerType.Host, _host);
                    if (this.QueryIndex >= l)
                        this.QueryIndex -= l;
                    // var t = _headChunk.Temp();
                    return stringValue.Slice(l);
                }
            }
            return base.PathValue(stringValue);
        }
        public HttpMethod HttpMethod => Frist;



        public StringValue PathQuery
        {
            get
            {
                return Second;
            }
        }


        Uri _Referer;
        public Uri UrlReferrer
        {
            get
            {
                if (_Referer == null)
                {
                    if (this.Headers.TryGet(StringValue.Referer.Span, out var headValue))
                    {
                        try
                        {
                            _Referer = new Uri(headValue.Value.Span.UTF8());
                        }
                        catch
                        {

                        }

                    }
                }
                return _Referer;
            }
        }
        Uri _uri;
        public Uri Url
        {
            get
            {
                if (_uri == null)
                {
                    this._uri = new Uri($"{_scheme}://{_host}{this.Second}");
                }
                return this._uri;
            }
        }
        public void RewriteUrl(StringValue pathAndQuery)
        {
            this._uri = null;//new Uri(this.Url, pathAndQuery);
            this.Second = pathAndQuery;// new StringValue(pathAndQuery.UTF8());
            this.QueryIndex = this.Second.Span.IndexOf("?"u8);
        }
        String _UserHostAddress;
        public string UserHostAddress
        {
            get
            {
                if (String.IsNullOrEmpty(_UserHostAddress))
                {
                    switch (_context.RemoteIpAddress.Length)
                    {
                        case 4:
                        case 16:
                            _UserHostAddress = new IPAddress(_context.RemoteIpAddress.Span).ToString();
                            break;
                        default:
                            _UserHostAddress = "127.0.0.1";
                            break;
                    }
                }
                return _UserHostAddress;
            }
            protected set { _UserHostAddress = value; }
        }


        bool _IsUpgrade, _isWebSocket;

        public override bool IsWebSocket => _isWebSocket && _IsUpgrade;
        protected override void Headed()
        {
            if (this.IsHttpFormatError)
            {
                _context.OutText(400, "Bad Request");
            }
            else
            {
                netLoger.Write(HttpLogerType.Path, Method(this.Frist), this.PathQuery);
                if (_host.IsEmpty)
                {
                    _host = _context.Host;
                }

                _context.PrepareRespone(this);
            }


        }
        public virtual void NotCounted()
        {
        }
        byte[] _lastFormBuffer = Array.Empty<byte>();
        int lastFormBufferSize = 0;
        StringValue FormKey = StringValue.Empty;
        HeadValues _from = new HeadValues();

        public void ReadAsForm(bool IsMonitor, Action<HeadValues> action)
        {
            if (this.ContentType.Span.IndexOf("form-urlencoded"u8, true) > -1)
            {
                this.ReadAsData(IsMonitor, (b, i, c) =>
                {
                    if (b.Length == 0)
                    {
                        if (i == 0 && c == 0)
                        {
                            this.FormValue(b, i, c);
                        }
                        if (_lastFormBuffer.Length > 0)
                        {
                            ArrayPool<byte>.Shared.Return(_lastFormBuffer);
                            lastFormBufferSize = 0;
                            _lastFormBuffer = Array.Empty<byte>();
                        }

                        action(_from);

                    }
                    else
                    {
                        this.FormValue(b, i, c);
                    }
                });
            }
            else
            {
                action(_from);
            }
        }

        public HeadValues Form
        {
            get
            {
                return _from;
            }
        }
        void FormValue(byte[] data, int offset, int size)
        {
            for (int i = 0; i < size; i++)
            {
                switch (data[offset + i])
                {
                    case 0x26:
                        StringValue value = StringValue.Empty;
                        if (lastFormBufferSize > 0)
                        {
                            // Uri.d
                            value = new StringValue(WebUtility.UrlDecodeToBytes(_lastFormBuffer, 0, lastFormBufferSize));

                        }
                        if (FormKey.IsEmpty == false)
                        {
                            _from.Add(new HeadValue(FormKey, value));
                            FormKey = StringValue.Empty;

                        }
                        else if (value.IsEmpty == false)
                        {
                            _from.Add(new HeadValue(StringValue.Empty, value));
                        }
                        lastFormBufferSize = 0;
                        break;
                    case 0x3d:
                        FormKey = lastFormBufferSize == 0 ? StringValue.Empty : new StringValue(WebUtility.UrlDecodeToBytes(_lastFormBuffer, 0, lastFormBufferSize));
                        lastFormBufferSize = 0;
                        break;
                    default:
                        if (lastFormBufferSize == _lastFormBuffer.Length)
                        {
                            var b = ArrayPool<byte>.Shared.Rent(lastFormBufferSize + 0x100);
                            Array.Copy(_lastFormBuffer, 0, b, 0, lastFormBufferSize);
                            if (_lastFormBuffer.Length > 0)
                            {
                                ArrayPool<byte>.Shared.Return(_lastFormBuffer);
                            }
                            _lastFormBuffer = b;
                        }
                        _lastFormBuffer[lastFormBufferSize] = data[offset + i];
                        lastFormBufferSize++;
                        break;
                }
            }
            if (offset == 0 && size == 0 && data.Length == 0)
            {
                StringValue value = StringValue.Empty;
                if (lastFormBufferSize > 0)
                {
                    value = new StringValue(WebUtility.UrlDecodeToBytes(_lastFormBuffer, 0, lastFormBufferSize));

                }
                if (FormKey.IsEmpty == false)
                {
                    _from.Add(new HeadValue(FormKey, value));

                }
                else if (value.IsEmpty == false)
                {
                    _from.Add(new HeadValue(StringValue.Empty, value));
                }
                lastFormBufferSize = 0;
            }
        }
        public StringValue Path
        {
            get
            {
                if (this.QueryIndex == -1)
                {
                    return Second;
                }
                else
                {
                    return Second.Slice(0, this.QueryIndex);
                }
            }
        }
        public StringValue Query
        {
            get
            {
                if (this.QueryIndex == -1)
                {
                    return StringValue.Empty;
                }
                else
                {
                    return Second.Slice(this.QueryIndex);
                }

            }
        }
        Net.NetWriteData _readData;
        // NetShare netShare;
        System.Threading.ManualResetEvent _bodyreset;

        protected override void Body(byte[] data, int offset, int size)
        {
            if (size > 0 && this.IsHttpFormatError == false && this.isBodyFinish == false)
            {
                if (_readData == null)
                {
                    if (this.Memory.MaxSize >= (this.Memory.TempSize + this.Memory.Size + size))
                    {
                        this.Memory.Temp(data.AsSpan(offset, size));//,offset,size)
                    }
                    else
                    {
                        _bodyreset = new ManualResetEvent(false);
                        _bodyreset.WaitOne(10000);
                        _bodyreset.Dispose();
                        _bodyreset = null;
                        _readData?.Invoke(data, offset, size);

                    }

                }
                else
                {
                    _readData.Invoke(data, offset, size);

                }
            }
        }
        protected override void ReceiveError(Exception ex)
        {
            // _bodyreset?.Set();
            _readData?.Invoke(Array.Empty<byte>(), -1, 0);
            _context.OutText(500, ex?.Message ?? "网络异常");
            // _context.OutputFinish();
        }
        class Monitor
        {
            Net.NetWriteData _writeData;
            NetLoger _body;
            public Monitor(NetLoger monitor, Net.NetWriteData writeData)
            {
                this._body = monitor;
                this._writeData = writeData;
            }
            public void Body(byte[] data, int offset, int size)
            {
                if (data.Length > 0)
                {
                    this._body.Write(data.AsSpan(offset, size));
                }
                else
                {
                    this._body.End();
                }
                this._writeData(data, offset, size);
            }
        }
        public void ReadAsData(bool IsMonitor, Net.NetWriteData readData)
        {
            if (this.IsHttpFormatError)
            {
                readData(Array.Empty<byte>(), -1, 0);
            }
            else
            {

                if (IsMonitor && this.ContentType.Contains("form-urlencoded"u8, true))
                {
                    readData = new Monitor(this.netLoger.Body(), readData).Body;
                }

                if (this.Memory.TempSize > 0)
                {
                    readData.Invoke(this.Memory.Chunk, this.Memory.Size, this.Memory.TempSize);
                    this.Memory.Scale(this.Memory.TempSize);
                }


                this._readData = readData;
                _bodyreset?.Set();
                if (this.isBodyFinish)
                {
                    readData(Array.Empty<byte>(), 0, 0);
                    this._readData = null;
                }
            }
        }

        public override void Finish()
        {
            this.isBodyFinish = true;
            this._readData?.Invoke(Array.Empty<byte>(), 0, 0);
            this._readData = null;
        }
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            _Cookies.Dispose();
            // _bodyreset?.Dispose();
        }

        bool isBodyFinish = false;

    }

}
